package com.gss.plugin.oauth2.filter;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gss.entity.R;
import com.gss.entity.enums.ConstantEnum;
import com.gss.entity.enums.constants.ConstantCode;
import com.gss.plugin.oauth2.config.Oauth2Properties;
import com.gss.plugin.oauth2.exception.CustomAuthException;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 自定义JWT认证过滤器
 * 该类继承自BasicAuthenticationFilter，在doFilterInternal方法中，
 * 从http头的Authorization 项读取token数据，然后用Jwts包提供的方法校验token的合法性。
 * 如果校验通过，就认为这是一个取得授权的合法请求
 * @author zhaoxinguo on 2017/9/13.
 */
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

    private final Oauth2Properties oauthConfigProp;

    public JWTAuthenticationFilter(Oauth2Properties oauthConfigProp,AuthenticationManager authenticationManager) {
        super(authenticationManager);
        this.oauthConfigProp = oauthConfigProp;
    }

    /**
     * 拦截请求，认证
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//        String requestUri= request.getRequestURI();
//        List<String> authWhitelist = oauthConfigProp.getAuthWhitelist();
//        if (authWhitelist.contains(requestUri)) {
//            chain.doFilter(request, response);
//            return;
//        }

        String header = request.getHeader(oauthConfigProp.getTokenName());
        if (header == null) {
            // token 不存在
            this.authError(response,ConstantEnum.TOKEN_MISS);
        }else if (!header.startsWith(oauthConfigProp.getTokenPrefix())) {
            // token 格式错误
            this.authError(response,ConstantEnum.TOKEN_JWT_ERROR);
        }else {
            // 解析token，判断token是否合格
            String token = header.substring(oauthConfigProp.getTokenPrefix().length() + 1);
            Claims claims = this.parseToken(token,response);
            if (claims != null) {
                chain.doFilter(request, response);
            }
        }
    }

    /**
     * 解析token
     *
     * @param token
     * @param response
     * @return
     */
    private Claims parseToken(String token, HttpServletResponse response) throws IOException {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(oauthConfigProp.getSignKey().getBytes(StandardCharsets.UTF_8))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            log.error("【token拦截器】 token 过期 ",e);
            // token 过期
            this.authError(response,ConstantEnum.TOKEN_EXPIRE);
        } catch (UnsupportedJwtException | MalformedJwtException e) {
            log.error("【token拦截器】 token 格式错误 ",e);
            // token jwt 格式错误
            this.authError(response,ConstantEnum.TOKEN_JWT_ERROR);
        }catch (SignatureException e) {
            log.error("【token拦截器】 token 令牌错误 ",e);
            // token 令牌错误
            this.authError(response,ConstantEnum.TOKEN_SIGN_ERROR);
        }
        return claims;
    }

    /**
     * 认证失败响应
     * @param response
     * @param constant
     */
    private void authError(HttpServletResponse response, ConstantEnum constant) throws IOException {
        // 自定义返回格式内容
        R<Object> res = R.error(constant);
        res.setDesc("权限不足");

        response.setStatus(constant.getCode());
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=UTF-8");

        PrintWriter writer = response.getWriter();
        writer.write(new ObjectMapper().writeValueAsString(res));
        writer.flush();
        writer.close();
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            long start = System.currentTimeMillis();
            String token = request.getHeader(oauthConfigProp.getTokenName());
            if (ObjectUtil.isEmpty(token)) {
                throw new CustomAuthException(ConstantEnum.TOKEN_MISS);
            }
            // 解析token
            String user = null;
            token = token.replace(oauthConfigProp.getTokenPrefix(),"").replace(" ","");
            Claims claims = Jwts.parser()
                    .setSigningKey(oauthConfigProp.getSignKey())
                    .parseClaimsJws(token)
                    .getBody();
            log.info("相关信息 : {}",claims.getSubject());
            // token签发时间
            long issuedAt = claims.getIssuedAt().getTime();
            // 当前时间
            long currentTimeMillis = System.currentTimeMillis();
            // token过期时间
            long expirationTime = claims.getExpiration().getTime();
            // 1. 签发时间 < 当前时间 < (签发时间+((token过期时间-token签发时间)/2)) 不刷新token
            // 2. (签发时间+((token过期时间-token签发时间)/2)) < 当前时间 < token过期时间 刷新token并返回给前端
            // 3. tokne过期时间 < 当前时间 跳转登录，重新登录获取token
            // 验证token时间有效性
            if ((issuedAt + ((expirationTime - issuedAt) / 2)) < currentTimeMillis && currentTimeMillis < expirationTime) {

                // 重新生成token start
                Calendar calendar = Calendar.getInstance();
                Date now = calendar.getTime();
                // 设置签发时间
                calendar.setTime(new Date());
                // 设置过期时间
                calendar.add(Calendar.MINUTE, Long.bitCount(expirationTime));
                Date time = calendar.getTime();
                String refreshToken = Jwts.builder()
                        .setSubject(claims.getSubject())
                        .setIssuedAt(now)//签发时间
                        .setExpiration(time)//过期时间
                        .signWith(SignatureAlgorithm.HS512, oauthConfigProp.getSignKey()) //采用什么算法是可以自己选择的，不一定非要采用HS512
                        .compact();
                // 重新生成token end

                // 主动刷新token，并返回给前端
                response.addHeader(ConstantCode.REFRESH_TOKEN, refreshToken);
            }
            long end = System.currentTimeMillis();
            log.info("执行时间: " + (end - start) + " 毫秒");
            user = claims.getSubject();
            if (ObjectUtil.isNotEmpty(user)) {
                String authStr = user.split("-")[ConstantCode.INT_ONE];
                List<String> authList = JSON.parseArray(authStr, String.class);
                ArrayList<GrantedAuthority> authorities = new ArrayList<>();
                for (String auth : authList) {
                    authorities.add(new SimpleGrantedAuthority(auth));
                }
                return new UsernamePasswordAuthenticationToken(user, null, authorities);
            }
        } catch (ExpiredJwtException e) {
            e.printStackTrace();
            // 异常捕获、发送到ExpiredJwtException
            request.setAttribute("expiredJwtException", e);
            // 将异常分发到ExpiredJwtException控制器
            request.getRequestDispatcher("/expiredJwtException").forward(request, response);
        } catch (UnsupportedJwtException e) {
            e.printStackTrace();
            // 异常捕获、发送到UnsupportedJwtException
            request.setAttribute("unsupportedJwtException", e);
            // 将异常分发到UnsupportedJwtException控制器
            request.getRequestDispatcher("/unsupportedJwtException").forward(request, response);
        } catch (MalformedJwtException e) {
            e.printStackTrace();
            // 异常捕获、发送到MalformedJwtException
            request.setAttribute("malformedJwtException", e);
            // 将异常分发到MalformedJwtException控制器
            request.getRequestDispatcher("/malformedJwtException").forward(request, response);
        } catch (SignatureException e) {
            e.printStackTrace();
            // 异常捕获、发送到SignatureException
            request.setAttribute("signatureException", e);
            // 将异常分发到SignatureException控制器
            request.getRequestDispatcher("/signatureException").forward(request, response);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            // 异常捕获、发送到IllegalArgumentException
            request.setAttribute("illegalArgumentException", e);
            // 将异常分发到IllegalArgumentException控制器
            request.getRequestDispatcher("/illegalArgumentException").forward(request, response);
        }
        return null;
    }

}
